/*******************************************************************************
 * Copyright (c) 2012, 2017 ACIN, fortiss GmbH
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License 2.0 which is available at
 * http://www.eclipse.org/legal/epl-2.0.
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *  Alois Zoitl - initial API and implementation and/or initial documentation
 *******************************************************************************/
#include "posixsercommlayer.h"
#include "forte/util/devlog.h"
#include "forte/cominfra/commfb.h"
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "forte/arch/sockhand.h"
#include "forte/util/criticalregion.h"
#include "forte/cominfra/comlayersmanager.h"

using namespace forte::literals;

namespace forte::arch {
  namespace {
    [[maybe_unused]] const com_infra::ComLayerManager::EntryImpl<CPosixSerCommLayer> entry("ser"_STRID);
  }

  CPosixSerCommLayer::CPosixSerCommLayer(CComLayer *paUpperLayer, com_infra::CBaseCommFB *paFB) :
      CSerialComLayerBase(paUpperLayer, paFB) {
  }

  CPosixSerCommLayer::~CPosixSerCommLayer() {
    closeConnection();
  }

  com_infra::EComResponse CPosixSerCommLayer::sendData(void *paData, unsigned int paSize) {
    if (CFDSelectHandler::scmInvalidFileDescriptor != getSerialHandler()) {
      ssize_t nToSend = paSize;
      while (0 < nToSend) {
        ssize_t nSentBytes = write(getSerialHandler(), paData, nToSend);
        if (nSentBytes <= 0) {
          DEVLOG_ERROR("CSerCommLayer: Send failed: %s\n", strerror(errno));
          return com_infra::e_ProcessDataSendFailed;
        }
        nToSend -= nSentBytes;
        paData = static_cast<char *>(paData) + nSentBytes;
      }
    }

    return com_infra::e_ProcessDataOk;
  }

  com_infra::EComResponse CPosixSerCommLayer::recvData(const void *, unsigned int) {
    util::CCriticalRegion lock(mRecvLock);
    ssize_t nReadCount = read(getSerialHandler(), &mRecvBuffer[mBufFillSize], cgIPLayerRecvBufferSize - mBufFillSize);

    switch (nReadCount) {
      case 0:
        DEVLOG_INFO("Connection closed by peer\n");
        mInterruptResp = com_infra::e_InitTerminated;
        closeConnection();
        break;
      case -1:
        DEVLOG_ERROR("CSerCommLayer: read failed: %s\n", strerror(errno));
        mInterruptResp = com_infra::e_ProcessDataRecvFaild;
        break;
      default:
        // we successfully received data
        mBufFillSize += unsigned(nReadCount);
        mInterruptResp = com_infra::e_ProcessDataOk;
        break;
    }

    mFb->interruptCommFB(this);
    return mInterruptResp;
  }

  com_infra::EComResponse CPosixSerCommLayer::openSerialConnection(
      const SSerialParameters &paSerialParameters,
      CSerialComLayerBase<FORTE_SOCKET_TYPE, FORTE_INVALID_SOCKET>::TSerialHandleType *paHandleResult) {
    com_infra::EComResponse eRetVal = com_infra::e_ProcessDataNoSocket;

    // as first shot take the serial interface device as param (e.g., /dev/ttyS0 )
    CFDSelectHandler::TFileDescriptor fileDescriptor =
        open(paSerialParameters.interfaceName.c_str(), O_RDWR | O_NOCTTY);

    if (CFDSelectHandler::scmInvalidFileDescriptor != fileDescriptor) {
      tcgetattr(fileDescriptor, &mOldTIO);
      struct termios stNewTIO;
      memset(&stNewTIO, 0, sizeof(stNewTIO));

      stNewTIO.c_line = mOldTIO.c_line;

      switch (paSerialParameters.baudRate) {
        case e50: stNewTIO.c_cflag |= B50; break;
        case e75: stNewTIO.c_cflag |= B75; break;
        case e110: stNewTIO.c_cflag |= B110; break;
        case e134C5: stNewTIO.c_cflag |= B134; break;
        case e150: stNewTIO.c_cflag |= B150; break;
        case e200: stNewTIO.c_cflag |= B200; break;
        case e300: stNewTIO.c_cflag |= B300; break;
        case e600: stNewTIO.c_cflag |= B600; break;
        case e1200: stNewTIO.c_cflag |= B1200; break;
        case e1800: stNewTIO.c_cflag |= B1800; break;
        case e2400: stNewTIO.c_cflag |= B2400; break;
        case e4800: stNewTIO.c_cflag |= B4800; break;
        case e9600: stNewTIO.c_cflag |= B9600; break;
        case e19200: stNewTIO.c_cflag |= B19200; break;
        case e38400: stNewTIO.c_cflag |= B38400; break;
        case e57600: stNewTIO.c_cflag |= B57600; break;
        case e115200: stNewTIO.c_cflag |= B115200; break;
        case e1000000: stNewTIO.c_cflag |= B1000000; break;
        default: return com_infra::e_InitInvalidId; break;
      }

      cfsetispeed(&stNewTIO, cfgetispeed(&mOldTIO));
      cfsetospeed(&stNewTIO, cfgetospeed(&mOldTIO));

      switch (paSerialParameters.byteSize) {
        case e5: stNewTIO.c_cflag |= CS5; break;
        case e6: stNewTIO.c_cflag |= CS6; break;
        case e7: stNewTIO.c_cflag |= CS7; break;
        case e8: stNewTIO.c_cflag |= CS8; break;
        default: return com_infra::e_InitInvalidId; break;
      }

      switch (paSerialParameters.stopBits) {
        case eOneBit: stNewTIO.c_cflag &= ~CSTOPB; break;
        case eTwoBits: stNewTIO.c_cflag |= CSTOPB; break;
        default: return com_infra::e_InitInvalidId; break;
      }

      switch (paSerialParameters.parity) {
        case eNoParity: stNewTIO.c_cflag &= ~(PARENB | PARODD | CMSPAR); break;
        case eODD: stNewTIO.c_cflag |= PARENB | PARODD; break;
        case eEven:
          stNewTIO.c_cflag |= PARENB;
          stNewTIO.c_cflag &= ~PARODD;
          break;
        default: return com_infra::e_InitInvalidId; break;
      }

      stNewTIO.c_cflag &= ~CRTSCTS; // no hardware flow control

      stNewTIO.c_cflag |= (CLOCAL | CREAD); /* Local line - do not change "owner" of port |  Enable receiver*/

      stNewTIO.c_iflag = mOldTIO.c_iflag;
      stNewTIO.c_iflag |= IGNPAR; /* Map CR to NL | IGNPAR (was here before)  Ignore parity error. TODO: Should we
                                     delete this? It was on the old code*/
      ;
      stNewTIO.c_iflag &= ~(IXON | IXOFF | IXANY); // Turn off s/w flow ctrl
      stNewTIO.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | INPCK |
                            IUCLC); // Disable any special handling of received bytes

      stNewTIO.c_oflag = mOldTIO.c_oflag;
      stNewTIO.c_oflag &= ~(OPOST | ONLCR | OCRNL);

      stNewTIO.c_lflag = mOldTIO.c_lflag;
      stNewTIO.c_lflag &= ~(ICANON | ECHO | ECHOE | ECHONL | ECHOK | ISIG | IEXTEN | CRTSCTS);

      stNewTIO.c_cc[VINTR] = _POSIX_VDISABLE; /* Ctrl-c */
      stNewTIO.c_cc[VQUIT] = _POSIX_VDISABLE; /* Ctrl-\ */
      stNewTIO.c_cc[VERASE] = _POSIX_VDISABLE; /* del */
      stNewTIO.c_cc[VKILL] = _POSIX_VDISABLE; /* @ */
      stNewTIO.c_cc[VEOF] = _POSIX_VDISABLE; /* Ctrl-d */
      stNewTIO.c_cc[VTIME] = 10; /* inter-character timer unused */
      stNewTIO.c_cc[VMIN] = 1; /* blocking read until 1 character arrives */
      stNewTIO.c_cc[VSWTC] = _POSIX_VDISABLE; /* '\0' */
      stNewTIO.c_cc[VSTART] = _POSIX_VDISABLE; /* Ctrl-q */
      stNewTIO.c_cc[VSTOP] = _POSIX_VDISABLE; /* Ctrl-s */
      stNewTIO.c_cc[VSUSP] = _POSIX_VDISABLE; /* Ctrl-z */
      stNewTIO.c_cc[VEOL] = _POSIX_VDISABLE; /* '\0' */
      stNewTIO.c_cc[VREPRINT] = _POSIX_VDISABLE; /* Ctrl-r */
      stNewTIO.c_cc[VDISCARD] = _POSIX_VDISABLE; /* Ctrl-u */
      stNewTIO.c_cc[VWERASE] = _POSIX_VDISABLE; /* Ctrl-w */
      stNewTIO.c_cc[VLNEXT] = _POSIX_VDISABLE; /* Ctrl-v */
      stNewTIO.c_cc[VEOL2] = _POSIX_VDISABLE; /* '\0' */

      tcflush(fileDescriptor, TCIFLUSH);
      tcsetattr(fileDescriptor, TCSANOW, &stNewTIO);

      getExtEvHandler<CFDSelectHandler>().addComCallback(fileDescriptor, this);
      *paHandleResult = fileDescriptor;
      eRetVal = com_infra::e_InitOk;

    } else {
      eRetVal = com_infra::e_ProcessDataInvalidObject;
      DEVLOG_ERROR("CSerCommLayer: open failed: %s\n", strerror(errno));
    }

    return eRetVal;
  }

  void CPosixSerCommLayer::closeConnection() {
    CFDSelectHandler::TFileDescriptor fileDescriptor = getSerialHandler();
    if (CFDSelectHandler::scmInvalidFileDescriptor != fileDescriptor) {
      getExtEvHandler<CFDSelectHandler>().removeComCallback(fileDescriptor);
      tcsetattr(fileDescriptor, TCSANOW, &mOldTIO);
      close(fileDescriptor);
    }
  }
} // namespace forte::arch
